Completed
Pull Request — master (#99)
by
unknown
01:16
created

BtccomConverter.convertAddressUnspentOutputs   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
c 2
b 0
f 0
nc 1
dl 0
loc 24
rs 8.9713
nop 2

1 Function

Rating   Name   Duplication   Size   Complexity  
A 0 12 1
1
var RestClient = require('./rest_client');
2
var Wallet = require('./wallet');
3
var blocktrail = require('./blocktrail');
4
var bitcoinJS = require('bitcoinjs-lib');
5
6
var BtccomConverter = function(network, useNewCashAddr) {
7
    this.network = network;
8
    this.useNewCashAddr = useNewCashAddr;
9
10
    this.specialRawTxClientOnly = new RestClient({
11
        host: "btc.com",
12
        https: true,
13
        port: 443,
14
        endpoint: "/"
15
    });
16
};
17
18
function getAddressScriptInfo(address, network, useNewCashAddr) {
19
    try {
20
        var addressScriptInfo = bitcoinJS.address.toOutputScript(address, network, useNewCashAddr);
21
    } catch (e) {
22
        addressScriptInfo = null;
23
    }
24
    return addressScriptInfo;
25
}
26
27
function getScriptAsm(chunks) {
28
29
    try {
30
        var scriptAsm = bitcoinJS.script.toASM(chunks);
31
    } catch (e) {
32
        scriptAsm = null;
33
    }
34
    return scriptAsm;
35
}
36
37
function prettifyAsm(asm) {
38
    if (!asm) {
39
        return asm;
40
    }
41
42
    return asm.replace(/^0 /, "OP_0 ");
43
}
44
45
function getType(script) {
46
    try {
47
        var type = bitcoinJS.script.classifyOutput(script);
48
    } catch (e) {
49
        type = null;
50
    }
51
    return type;
52
}
53
54
function getBase58AddressHash160(address, network, useNewCashAddr) {
55
    try {
56
        var addressInfo = Wallet.getAddressAndType(address, network, useNewCashAddr);
57
    } catch (e) {
58
        return null;
59
    }
60
61
    if (addressInfo.type === "base58") {
62
        return addressInfo.decoded.hash;
63
    } else if (addressInfo.type === "bech32") {
64
        if (addressInfo.data.length === 20) {
65
            return addressInfo.decoded.hash;
66
        }
67
        return null;
68
    } else if (addressInfo.type === "") {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if addressInfo.type === "" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
69
        return addressInfo.decoded.hash;
70
    }
71
}
72
73
function convertBtccomOutputScriptType(scriptType) {
74
    switch (scriptType) {
75
        case "P2PKH_PUBKEY":
76
            return "pubkey";
77
        case "P2PKH":
78
            return "pubkeyhash";
79
        case "P2SH":
80
            return "scripthash";
81
        case "P2WSH_V0":
82
            return "witnessscripthash";
83
        case "P2WPKH":
84
            return "witnesspubkeyhash";
85
        case "coinbase":
86
            return "coinbase";
87
        default:
88
            throw new Error("Not implemented yet, script type: " + scriptType)
89
    }
90
}
91
92
function utcTimestampToISODateStr(time) {
93
    return (new Date(time * 1000)).toISOString().replace(/\.000Z$/, '+0000');
94
}
95
96
function flattenAddresses(addrs) {
97
    if (!addrs) {
98
        return addrs;
99
    } else if (addrs.length === 1) {
100
        return addrs[0];
101
    } else {
102
        return addrs;
103
    }
104
}
105
106
BtccomConverter.prototype.paginationParams = function(params) {
107
    if (!params) {
108
        return params;
109
    }
110
111
    if (typeof params.limit !== "undefined") {
112
        params.pagesize = params.limit;
113
        delete params.limit;
114
    }
115
116
    return params;
117
};
118
119
BtccomConverter.prototype.getUrlForBlock = function(blockHash) {
120
    return "/block/" + blockHash;
121
};
122
123
BtccomConverter.prototype.getUrlForTransaction = function(txId) {
124
    return "/tx/" + txId + "?verbose=3"
125
};
126
127
BtccomConverter.prototype.getUrlForTransactions = function(txIds) {
128
    return "/tx/" + txIds.join(",") + "?verbose=3"
129
};
130
131
BtccomConverter.prototype.getUrlForBlockTransaction = function(blockHash) {
132
    return "/block/" + blockHash + "/tx"
133
};
134
135
BtccomConverter.prototype.getUrlForAddress = function(address) {
136
    return "/address/" + address;
137
};
138
139
BtccomConverter.prototype.getUrlForAddressTransactions = function(address) {
140
    return "/address/" + address + "/tx?verbose=3";
141
};
142
143
BtccomConverter.prototype.getUrlForAddressUnspent = function(address) {
144
    return "/address/" + address + "/unspent";
145
};
146
147
BtccomConverter.prototype.getUrlForAllBlocks = function() {
148
    return "/all-blocks";
149
};
150
151
BtccomConverter.prototype.handleErros = function(self, data) {
152
    if (self.converter instanceof BtccomConverter) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if self.converter instanceof BtccomConverter is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
153
        if (data.err_no === 0 || data.data !== null) {
154
            return data;
155
        } else {
156
            return {
157
                err_no: data.err_no,
158
                err_msg: data.err_msg,
159
                data: data.data
160
            }
161
        }
162
    }
163
}
164
165
BtccomConverter.prototype.convertBlock = function(oldData) {
166
    var data = {};
167
    data.hash = oldData.data.hash;
168
    data.version = oldData.data.version;
169
    data.height = oldData.data.height;
170
    data.block_time = utcTimestampToISODateStr(oldData.data.timestamp);
171
    data.arrival_time = utcTimestampToISODateStr(oldData.data.timestamp);
172
    data.bits = oldData.data.bits;
173
    data.nonce = oldData.data.nonce;
174
    data.merkleroot = oldData.data.mrkl_root;
175
    data.prev_block = oldData.data.prev_block_hash;
176
    data.next_block = oldData.data.next_block_hash ;
177
    data.byte_size = oldData.data.size;
178
    data.difficulty = Math.floor(oldData.data.difficulty);
179
    data.transactions = oldData.data.tx_count;
180
    data.reward_block = oldData.data.reward_block;
181
    data.reward_fees = oldData.data.reward_fees;
182
    data.created_at = oldData.data.created_at;
183
    data.confirmations = oldData.data.confirmations;
184
    data.is_orphan = oldData.data.is_orphan ;
185
    data.is_sw_block = oldData.data.is_sw_block;
186
    data.byte_size = oldData.data.stripped_size;
187
    data.weight = oldData.data.weight;
188
    data.miningpool_name = oldData.data.miningpool_name ? oldData.data.miningpool_name : null;
189
    data.miningpool_url  = oldData.data.miningpool_url ?  oldData.data.miningpool_url : null;
190
    data.miningpool_slug = oldData.data.miningpool_slug ? oldData.data.miningpool_slug : null;
191
192
    return data;
193
};
194
195
196
BtccomConverter.prototype.convertBlockTxs = function(oldData) {
197
198
    var list = [];
199
    oldData.data.list.forEach(function(oldTx) {
200
        var resTx = convertBtccomTxToBlocktrail(oldTx);
201
202
        list.push(resTx);
203
    });
204
205
    return {
206
        data: list,
207
        current_page: oldData.data.page,
208
        per_page: oldData.data.pagesize,
209
        total: oldData.data.total_count
210
    };
211
};
212
213
function convertBtccomTxToBlocktrail(tx) {
214
    var data = {};
215
216
    data.size = tx.vsize;
217
    data.hash = tx.hash;
218
    data.block_height = tx.block_height ;
219
    data.block_time = utcTimestampToISODateStr(tx.block_time);
220
    data.block_hash = null; // TODO: missing
221
    data.confirmations = tx.confirmations;
222
    data.is_coinbase = tx.is_coinbase;
223
224
    var totalInputValue;
225
    if (data.is_coinbase) {
226
        totalInputValue = tx.outputs[0].value - tx.fee
227
    } else {
228
        totalInputValue = tx.inputs_value;
229
    }
230
231
    data.total_input_value = totalInputValue;
232
    data.total_output_value = tx.outputs.reduce(function(total, output) {
233
        return total + output.value;
234
    }, 0);
235
    data.total_fee = tx.fee;
236
    data.inputs = [];
237
    data.outputs = [];
238
    data.opt_in_rbf = false;
239
240
    for (var inputIdx in tx.inputs) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
241
        var input = tx.inputs[inputIdx];
242
        var scriptType;
243
        var inputValue;
244
        var inputTxid;
245
        var outpointIdx;
246
247
        if (input.sequence < bitcoinJS.Transaction.DEFAULT_SEQUENCE - 1) {
248
            data.opt_in_rbf = true;
249
        }
250
251
        if (data.is_coinbase && input.prev_position == -1 &&
252
            input.prev_tx_hash == "0000000000000000000000000000000000000000000000000000000000000000") {
253
            scriptType = "coinbase";
254
            inputTxid = null;
255
            inputValue = totalInputValue;
256
            outpointIdx = 0xffffffff;
257
        } else {
258
            scriptType = input.prev_type;
259
            inputValue = input.prev_value;
260
            inputTxid = input.prev_tx_hash;
261
            outpointIdx = input.prev_position;
262
        }
263
264
        data.inputs.push({
265
            index: parseInt(inputIdx, 10),
266
            output_hash: inputTxid,
267
            output_index: outpointIdx,
268
            value: inputValue,
269
            sequence: input.sequence,
270
            address: flattenAddresses(input.prev_addresses),
271
            type: convertBtccomOutputScriptType(scriptType),
272
            script_signature: input.script_hex
273
        });
274
    }
275
276
    for (var outIdx in tx.outputs) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
277
        var output = tx.outputs[outIdx];
278
279
        data.outputs.push({
280
            index: parseInt(outIdx, 10),
281
            value: output.value,
282
            address: flattenAddresses(output.addresses),
283
            type: convertBtccomOutputScriptType(output.type),
284
            script: prettifyAsm(output.script_asm),
285
            script_hex: output.script_hex,
286
            spent_hash: output.spent_by_tx,
287
            spent_index: output.spent_by_tx_position
288
        });
289
    }
290
291
    data.size = tx.size;
292
    data.is_double_spend = tx.is_double_spend;
293
294
    data.lock_time_timestamp = null;
295
    data.lock_time_block_height = null;
296
    if (tx.lock_time) {
297
        if (tx.lock_time < blocktrail.LOCK_TIME_TIMESTAMP_THRESHOLD) {
298
            data.lock_time_block_height = tx.lock_time;
299
        } else {
300
            data.lock_time_timestamp = tx.lock_time;
301
        }
302
    }
303
304
    // Extra fields from Btc.com
305
    data.is_sw_tx = tx.is_sw_tx;
306
    data.weight = tx.weight;
307
    data.witness_hash = tx.witness_hash;
308
    data.lock_time  = tx.lock_time;
309
    data.sigops = tx.sigops;
310
    data.version = tx.version;
311
312
    return data;
313
}
314
315
BtccomConverter.prototype.convertTx = function(oldData, rawTx) {
316
    var data = convertBtccomTxToBlocktrail(oldData.data);
317
    data.raw = rawTx;
318
    return data;
319
};
320
321
BtccomConverter.prototype.convertTxs = function(oldData) {
322
    var res = {};
323
324
    oldData.data
325
        .filter(function(tx) { return !!tx; })
326
        .forEach(function(oldTx) {
327
            var tx = convertBtccomTxToBlocktrail(oldTx);
328
            res[tx.hash] = tx;
329
        });
330
331
    return {data: res};
332
};
333
334
/// self.converter.convertAddressTxs(self.converter.handleResponse(data))
335
BtccomConverter.prototype.convertAddressTxs = function(oldData) {
336
    var data = oldData.data.list.map(function(tx) {
337
        var res = {};
338
339
        res.hash = tx.hash;
340
        res.time = utcTimestampToISODateStr(tx.block_time);
341
        res.confirmations = tx.confirmations;
342
        res.block_height = tx.block_height;
343
        res.block_hash = null; // @TODO
344
        res.is_coinbase = tx.is_coinbase;
345
        res.total_input_value = tx.inputs_value;
346
        res.total_output_value = tx.outputs_value;
347
        res.total_fee = tx.fee;
348
349
        res.inputs = tx.inputs.map(function(input, inIdx) {
350
            return {
351
                index: inIdx,
352
                output_hash: input.prev_tx_hash,
353
                output_index: input.prev_position,
354
                value: input.prev_value,
355
                address: flattenAddresses(input.prev_addresses),
356
                type:  res.is_coinbase ? res.is_coinbase : convertBtccomOutputScriptType(input.prev_type),
357
                script_signature: input.script_hex
358
            };
359
        });
360
361
        res.outputs = tx.outputs.map(function(output, outIdx) {
362
            return {
363
                index: outIdx,
364
                value: output.value,
365
                address: flattenAddresses(output.addresses),
366
                type: convertBtccomOutputScriptType(output.type),
367
                script: prettifyAsm(output.script_asm),
368
                spent_hash: output.spent_by_tx || null,
369
                script_hex: output.script_hex,
370
                spent_index: output.spent_by_tx_position
371
            };
372
        });
373
374
        // Extra fields from Btc.com
375
        res.is_double_spend = tx.is_double_spend;
376
        res.is_sw_tx = tx.is_sw_tx;
377
        res.weight = tx.weight;
378
        res.witness_hash = tx.witness_hash;
379
        res.version = tx.version;
380
381
        return res;
382
    });
383
384
    return {
385
        data: data,
386
        current_page: oldData.data.page,
387
        per_page: oldData.data.pagesize,
388
        total: oldData.data.total_count
389
    };
390
};
391
392
BtccomConverter.prototype.convertAddress = function(oldData) {
393
    var data = {};
394
395
    data.address = oldData.data.address;
396
    data.hash160 = getBase58AddressHash160(oldData.data.address, this.network, this.useNewCashAddr).toString("hex").toUpperCase();
397
    data.balance = oldData.data.balance;
398
    data.received = oldData.data.received;
399
    data.sent = oldData.data.sent;
400
    data.transactions = oldData.data.tx_count;
401
    data.utxos = oldData.data.unspent_tx_count;
402
    data.unconfirmed_received = oldData.data.unconfirmed_received;
403
    data.unconfirmed_sent = oldData.data.unconfirmed_sent;
404
    data.unconfirmed_transactions = oldData.data.unconfirmed_tx_count;
405
    data.first_tx = oldData.data.first_tx;
406
    data.last_tx = oldData.data.last_tx;
407
408
    return data;
409
};
410
411
BtccomConverter.prototype.convertAddressUnspentOutputs = function(oldData, address) {
412
    var script = getAddressScriptInfo(address, this.network, this.useNewCashAddr);
413
    var script_hex = script.toString("hex");
414
    var script_asm = getScriptAsm(script);
415
    var type = getType(script);
416
    var data = oldData.data.list.map(function(utxo) {
417
        return {
418
            hash: utxo.tx_hash,
419
            confirmations: utxo.confirmations,
420
            value: utxo.value,
421
            index: utxo.tx_output_n,
422
            address: address,
423
            type: type,
424
            script: script_asm,
425
            script_hex: script_hex
426
        };
427
    });
428
429
    return {
430
        data: data,
431
        current_page: oldData.data.page,
432
        total: oldData.data.total_count
433
    };
434
};
435
436
exports = module.exports = BtccomConverter;
437